/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 - 2008 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.basics.editor.gui.view.internal.panel;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.rmi.server.UID;

import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingConstants;

import com.genia.toolbox.basics.editor.gui.AbstractGUI;
import com.genia.toolbox.basics.editor.gui.panel.AbstractPanel;
import com.genia.toolbox.basics.editor.gui.view.internal.AbstractDocumentInternalFrame;
import com.genia.toolbox.basics.editor.message.AbstractMessages;
import com.genia.toolbox.basics.editor.model.document.Element;
import com.genia.toolbox.basics.editor.model.document.ElementType;
import com.genia.toolbox.web.portlet.description.ContainerPortletDescription.LayoutDirection;

/**
 * A generic abstract view panel.
 * 
 * @param <G>
 *          The generic gui.
 * @param <I>
 *          The generic internal frame.
 * @param <E>
 *          The generic element.
 * @param <T>
 *          The generic type.
 */
@SuppressWarnings("serial")
public abstract class AbstractViewPanel<G extends AbstractGUI<?, ?, ?, ?, E, ?>, I extends AbstractDocumentInternalFrame<G, ?, ?, E, ?>, E extends Element<T>, T extends ElementType>
    extends AbstractPanel<G>
    implements ElementViewPanel<G, I, E, T>
{

  /**
   * The parent view panel (has to be a container).
   */
  private ElementViewPanel<G, I, E, T> parentPanel = null;

  /**
   * The internal frame.
   */
  private I internalFrame = null;

  /**
   * The type label.
   */
  private JLabel labelType = null;

  /**
   * The ID label.
   */
  private JLabel labelId = null;

  /**
   * The common panel.
   */
  private JPanel commonPanel = null;

  /**
   * The specific panel.
   */
  private JPanel specificPanel = null;

  /**
   * The selected panel.
   */
  private JPanel selectedPanel = null;

  /**
   * The popup menu.
   */
  private JPopupMenu popup = null;

  /**
   * The add element menu.
   */
  private JMenuItem menuAdd = null;

  /**
   * The delete element menu.
   */
  private JMenuItem menuDelete = null;

  /**
   * The move up element menu.
   */
  private JMenuItem menuUp = null;

  /**
   * The move down element menu.
   */
  private JMenuItem menuDown = null;

  /**
   * The move left element menu.
   */
  private JMenuItem menuLeft = null;

  /**
   * The move right element menu.
   */
  private JMenuItem menuRight = null;

  /**
   * The displayed element model.
   */
  private E elementModel = null;



  /**
   * Constructor of a root panel.
   * 
   * @param gui
   *          the gui.
   * @param internalFrame
   *          the internalFrame.
   */
  public AbstractViewPanel(G gui, I internalFrame)
  {
    super(gui);
    this.parentPanel = null;
    this.internalFrame = internalFrame;
  }



  /**
   * Constructor of a sub-panel.
   * 
   * @param internalFrame
   *          the internal frame.
   * @param parentPanel
   *          the parent panel.
   */
  public AbstractViewPanel(I internalFrame, ElementViewPanel<G, I, E, T> parentPanel)
  {
    super(parentPanel.getGui());
    this.parentPanel = parentPanel;
    this.internalFrame = internalFrame;
  }



  /**
   * Get the parent panel.
   * 
   * @return the parent panel.
   */
  public ElementViewPanel<G, I, E, T> getParentPanel()
  {
    return this.parentPanel;
  }



  /**
   * Set the parent panel.
   * 
   * @param parentPanel
   *          the parent panel.
   */
  public void setParentPanel(ElementViewPanel<G, I, E, T> parentPanel)
  {
    this.parentPanel = parentPanel;
  }



  /**
   * Get the internal frame.
   * 
   * @return the internal frame.
   */
  public I getInternalFrame()
  {
    return this.internalFrame;
  }



  /**
   * Initialise the panel.
   */
  public void initialise()
  {

    this.createPopupMenu();

    GridBagLayout layout = new GridBagLayout();
    GridBagConstraints constraints = new GridBagConstraints();
    this.setLayout(layout);

    this.commonPanel = this.createCommonPanel();
    this.commonPanel.addMouseListener(this);

    this.specificPanel = this.createSpecificPanel();

    if (this.specificPanel != null) {
      constraints.gridx = 1;
      constraints.gridy = 1;
      constraints.gridwidth = 1;
      constraints.gridheight = 1;
      constraints.weightx = 1;
      constraints.weighty = 0;
      constraints.fill = GridBagConstraints.BOTH;
      constraints.insets = new Insets(5, 5, 0, 5);
      constraints.anchor = GridBagConstraints.NORTH;
      this.add(this.commonPanel, constraints);

      constraints.gridx = 1;
      constraints.gridy = 2;
      constraints.gridwidth = 1;
      constraints.gridheight = 1;
      constraints.weightx = 1;
      constraints.weighty = 1;
      constraints.fill = GridBagConstraints.BOTH;
      constraints.insets = new Insets(0, 5, 5, 5);
      constraints.anchor = GridBagConstraints.CENTER;
      this.add(this.specificPanel, constraints);
    }
    else {
      constraints.gridx = 1;
      constraints.gridy = 1;
      constraints.gridwidth = 1;
      constraints.gridheight = 1;
      constraints.weightx = 1;
      constraints.weighty = 1;
      constraints.fill = GridBagConstraints.BOTH;
      constraints.insets = new Insets(5, 5, 5, 5);
      this.add(this.commonPanel, constraints);
    }
  }



  /**
   * Create the common panel.
   * 
   * @return the common panel.
   */
  public JPanel createCommonPanel()
  {
    JPanel panel = new JPanel();

    GridBagLayout layout = new GridBagLayout();
    GridBagConstraints constraints = new GridBagConstraints();
    panel.setLayout(layout);

    this.labelType = new JLabel();
    this.labelType.setHorizontalAlignment(SwingConstants.CENTER);
    this.labelType.setVerticalAlignment(SwingConstants.TOP);
    this.labelType.setVerticalTextPosition(SwingConstants.TOP);
    constraints.gridx = 1;
    constraints.gridy = 1;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 1;
    constraints.weighty = 1;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.insets = new Insets(5, 0, 0, 0);
    constraints.anchor = GridBagConstraints.NORTH;
    panel.add(this.labelType, constraints);

    this.labelId = new JLabel();
    this.labelId.setHorizontalAlignment(SwingConstants.CENTER);
    this.labelId.setVerticalAlignment(SwingConstants.TOP);
    this.labelId.setVerticalTextPosition(SwingConstants.TOP);
    constraints.gridx = 1;
    constraints.gridy = 2;
    constraints.gridwidth = 1;
    constraints.gridheight = 1;
    constraints.weightx = 1;
    constraints.weighty = 1;
    constraints.fill = GridBagConstraints.HORIZONTAL;
    constraints.insets = new Insets(5, 0, 5, 0);
    constraints.anchor = GridBagConstraints.NORTH;
    panel.add(this.labelId, constraints);

    return panel;
  }



  /**
   * Create the specific panel.
   * 
   * @return the specific panel.
   */
  public abstract JPanel createSpecificPanel();



  /**
   * Create the element type.
   * 
   * @return the element type.
   */
  public abstract T getElementType();



  /**
   * Deactivate the whole panel.
   */
  public void deactivateAll()
  {
    this.activateCommon(false);
    this.selectedPanel = null;
  }



  /**
   * Handle the common panel activation.
   * 
   * @param isActivated
   *          Whether the panel is activated.
   */
  public void activateCommon(boolean isActivated)
  {
    if (isActivated) {
      this.selectedPanel = this.commonPanel;
      this.getInternalFrame().setSelectedElement(this.getElement());
    }
    this.activate(isActivated, this.commonPanel);
  }



  /**
   * Deactivate the specific panel.
   */
  public void deactivateSpecific()
  {
    // Do nothing.
  }



  /**
   * Deactivate the parent panel.
   */
  public void deactivateParent()
  {
    this.deactivateParent(this.getParentPanel());
  }



  /**
   * Deactivate the parent.
   * 
   * @param parent
   *          the specified parent.
   */
  private void deactivateParent(ElementViewPanel<G, I, E, T> parent)
  {
    if (parent != null) {
      parent.deactivateAll();
      if (parent.getParentPanel() != null) {
        this.deactivateParent(parent.getParentPanel());
      }
    }
  }



  /**
   * Activate a panel.
   * 
   * @param isActivated
   *          Whether the panel should be activated.
   * @param panel
   *          The panel.
   */
  protected void activate(boolean isActivated, JPanel panel)
  {
    Color background = AbstractGUI.COLOR_PANEL_ERROR;
    if (isActivated) {
      background = AbstractGUI.COLOR_PANEL;
    }
    if (panel != null) {
      panel.setBackground(background);
    }
  }



  /**
   * Handle mouse clicked events.
   * 
   * @param e
   *          the mouse event.
   */
  public void mouseClicked(MouseEvent e)
  {
    Object source = e.getSource();
    this.deactivateParent();
    if (source.equals(this.commonPanel)) {
      if (this.commonPanel.equals(this.selectedPanel)) {
        if (!this.elementModel.isSubElement()) {
          this.deactivateAll();
          this.getInternalFrame().setSelectedElement(null);
          this.getGui().displayCurrentDocumentSettings();
        }
      }
      else {
        this.activateCommon(true);
        this.deactivateSpecific();
        this.getGui().displayElementSettings(this.elementModel);
      }
    }
    else {
      this.specificPanel = null;
    }
  }



  /**
   * Handle mouse entered events.
   * 
   * @param e
   *          the mouse event.
   */
  public void mouseEntered(MouseEvent e)
  {
    // Do nothing
  }



  /**
   * Handle mouse exited events.
   * 
   * @param e
   *          the mouse event.
   */
  public void mouseExited(MouseEvent e)
  {
    // Do nothing
  }



  /**
   * Handle mouse pressed events.
   * 
   * @param e
   *          the mouse event.
   */
  public void mousePressed(MouseEvent e)
  {
    this.maybeShowPopup(e);
  }



  /**
   * Handle mouse released events.
   * 
   * @param e
   *          the mouse event.
   */
  public void mouseReleased(MouseEvent e)
  {
    this.maybeShowPopup(e);
  }



  /**
   * Update the panel border whether the element model is accepted or not.
   */
  public void updateBorder()
  {
    if (this.elementModel != null && this.elementModel.accept()) {
      this.setBorder(BorderFactory.createLineBorder(AbstractGUI.COLOR_BORDER, AbstractGUI.BORDER_WIDTH));
    }
    else {
      this.setBorder(BorderFactory.createLineBorder(AbstractGUI.COLOR_BORDER_ERROR, AbstractGUI.BORDER_WIDTH));
    }
  }



  /**
   * Set the displayed element.
   * 
   * @param model
   *          the displayed element model.
   */
  public void setElement(E model)
  {
    this.elementModel = model;
    String type = "";
    if (model.getType() != null) {
      type = model.getType().toString().toUpperCase();
    }
    this.labelType.setText(type);
    this.labelType.setFont(new Font(this.labelType.getFont().getName(), Font.BOLD, this.labelType.getFont().getSize()));
    this.labelId.setText(this.getLabelIdText(model));
    this.updateBorder();
  }



  /**
   * Get the id label text.
   * 
   * @param model
   *          the model.
   * @return the id label text.
   */
  public abstract String getLabelIdText(E model);



  /**
   * Update the displayed element.
   * 
   * @param updatedModel
   *          the displayed element model.
   */
  public void updateElement(E updatedModel)
  {
    if (this.elementModel.getUniqueID().equals(updatedModel.getUniqueID())) {
      this.setElement(updatedModel);
    }
    this.updateBorder();
  }



  /**
   * Get the displayed element.
   * 
   * @return the displayed element model.
   */
  public E getElement()
  {
    return this.elementModel;
  }



  /**
   * Set the ID label.
   * 
   * @param id
   *          the value to display.
   */
  public void setRootElementID(String id)
  {
    this.labelId.setText(id);
  }



  /**
   * Get the specific panel.
   * 
   * @return the specific panel.
   */
  public JPanel getSpecificPanel()
  {
    return this.specificPanel;
  }



  /**
   * Create the popup menu.
   * 
   * @return the popup menu.
   */
  public JPopupMenu createPopupMenu()
  {
    this.popup = new JPopupMenu();

    this.menuAdd = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_ADD));
    this.menuAdd.addActionListener(this);
    this.popup.add(this.menuAdd);

    this.menuDelete = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_DELETE));
    this.menuDelete.addActionListener(this);
    this.popup.add(this.menuDelete);

    this.popup.addSeparator();

    this.menuUp = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_UP));
    this.menuUp.addActionListener(this);
    this.popup.add(this.menuUp);

    this.menuDown = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_DOWN));
    this.menuDown.addActionListener(this);
    this.popup.add(this.menuDown);

    this.menuRight = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_RIGHT));
    this.menuRight.addActionListener(this);
    this.popup.add(this.menuRight);

    this.menuLeft = new JMenuItem(this.getGui().getController().notifyTranslation(AbstractMessages.BASIC_VIEW_PANEL_POPUP_MENU_LEFT));
    this.menuLeft.addActionListener(this);
    this.popup.add(this.menuLeft);

    return this.popup;
  }



  /**
   * Display if its needed the popup menu.
   * 
   * @param e
   *          The mouse event.
   */
  public void maybeShowPopup(MouseEvent e)
  {
    if (e.isPopupTrigger()) {
      this.displayPopup(e.getComponent(), e.getX(), e.getY());
    }
  }



  /**
   * Display the popup.
   * 
   * @param component
   *          The component.
   * @param x
   *          The x position.
   * @param y
   *          The y position.
   */
  public void displayPopup(Component component, int x, int y)
  {
    if (this.elementModel.isSubElement()) {
      this.getMenuAdd().setEnabled(false);
      this.getMenuDelete().setEnabled(true);
      if (LayoutDirection.HORIZONTAL.equals(this.elementModel.getParentDirection())) {
        this.getMenuDown().setEnabled(false);
        this.getMenuUp().setEnabled(false);
        this.getMenuRight().setEnabled(true);
        this.getMenuLeft().setEnabled(true);
      }
      else {
        this.getMenuDown().setEnabled(true);
        this.getMenuUp().setEnabled(true);
        this.getMenuRight().setEnabled(false);
        this.getMenuLeft().setEnabled(false);
      }
      this.popup.show(component, x, y);
    }
  }



  /**
   * Handle action events.
   * 
   * @param e
   *          the action event.
   */
  public void actionPerformed(ActionEvent e)
  {
    Object source = e.getSource();
    if (source.equals(this.menuDelete)) {
      this.deleteSubElement();
    }
    else if (source.equals(this.menuDown)) {
      this.moveDownOrLeft();
    }
    else if (source.equals(this.menuUp)) {
      this.moveUpOrRight();
    }
    else if (source.equals(this.menuLeft)) {
      this.moveDownOrLeft();
    }
    else if (source.equals(this.menuRight)) {
      this.moveUpOrRight();
    }
  }



  /**
   * Get the popup add element item.
   * 
   * @return the popup add element item.
   */
  public JMenuItem getMenuAdd()
  {
    return this.menuAdd;
  }



  /**
   * Get the popup delete element item.
   * 
   * @return the popup delete element item.
   */
  public JMenuItem getMenuDelete()
  {
    return this.menuDelete;
  }



  /**
   * Get the popup move down element item.
   * 
   * @return the popup move down element item.
   */
  public JMenuItem getMenuDown()
  {
    return this.menuDown;
  }



  /**
   * Get the popup move up element item.
   * 
   * @return the popup move up element item.
   */
  public JMenuItem getMenuUp()
  {
    return this.menuUp;
  }



  /**
   * Get the popup move left element item.
   * 
   * @return the popup move left element item.
   */
  public JMenuItem getMenuLeft()
  {
    return this.menuLeft;
  }



  /**
   * Get the popup move right element item.
   * 
   * @return the popup move right element item.
   */
  public JMenuItem getMenuRight()
  {
    return this.menuRight;
  }



  /**
   * Get the popup menu.
   * 
   * @return the popup menu.
   */
  public JPopupMenu getPopup()
  {
    return this.popup;
  }



  /**
   * Get the ID label.
   * 
   * @return the ID label.
   */
  public JLabel getLabelId()
  {
    return this.labelId;
  }



  /**
   * Get the type label.
   * 
   * @return the type label.
   */
  public JLabel getLabelType()
  {
    return this.labelType;
  }



  /**
   * Whether the common panel is selected.
   * 
   * @return the common label is selected.
   */
  public boolean isCommonSelected()
  {
    boolean isCommonSelected = false;
    if (this.commonPanel.equals(this.selectedPanel)) {
      isCommonSelected = true;
    }
    return isCommonSelected;
  }



  /**
   * Get the view panel from the element or one of its sub-element.
   * 
   * @param elementUniqueID
   *          The element unique ID.
   * @return the view panel.
   */
  public ElementViewPanel<G, I, E, T> getViewPanel(UID elementUniqueID)
  {
    ElementViewPanel<G, I, E, T> panel = null;
    if (this.getElement().getUniqueID().equals(elementUniqueID)) {
      panel = this;
    }
    return panel;
  }



  /**
   * Add a sub-element.
   */
  public abstract void addSubElement();



  /**
   * Delete a sub-element.
   */
  public abstract void deleteSubElement();



  /**
   * Move down or left a sub-element.
   */
  public abstract void moveDownOrLeft();



  /**
   * Move up or right a sub-element.
   */
  public abstract void moveUpOrRight();



  /**
   * Whether the common panel, or one of its sub panel is selected.
   * 
   * @return whether the common label or one of its sub panel is selected.
   */
  public boolean hasAtLeastOneCommonSelected()
  {
    return this.isCommonSelected();
  }

}
